﻿using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;

namespace Framework.DataTable
{
    public class BuildContext
    {
        public string ProjectPath = "";
        public string TempPath = "";
        public string SourcePath;
        //单独处理一个文件
        public string BuildFile;
        internal string ReadPath = "";
        internal string SourceClonePath;
        internal IDataTableProcessor Processor;
        internal List<string> Tables = new List<string>();
        internal List<ReadData> FileInfos = new List<ReadData>();
        internal List<ClassInfo> classInfos = new List<ClassInfo>();
        internal List<DataInfo> dataInfos = new List<DataInfo>();
        internal bool SkipGenCode;
        internal Regex NameRegex = new Regex(@"^[A-Za-z][A-Za-z0-9_#]*$");
        public BuildContext()
        {
            ProjectPath = Path.GetFullPath(Path.Combine(UnityEngine.Application.dataPath, "../"));
            TempPath = Path.GetFullPath(Path.Combine(UnityEngine.Application.temporaryCachePath, "DataTable"));
            SourcePath = Path.GetFullPath(Path.Combine(ProjectPath, "DataTable"));
            BuildFile = "";
        }
        internal void LogError(string message)
        {
            UnityEngine.Debug.LogError(message);
        }
        internal void LogWarning(string message)
        {
            UnityEngine.Debug.LogWarning(message);
        }
        public string GetTableName(string filePath)
        {
            var fileName = Path.GetFileNameWithoutExtension(filePath);
            //可以用 “_” 加前缀
            var part = fileName.Split('_');
            if (part.Length > 0)
            {
                fileName = part[part.Length - 1];
            }
            //可以用 “#” 加后缀
            var splitIndex = fileName.IndexOf("#");
            if (splitIndex > 0)
            {
                fileName = fileName.Substring(0, splitIndex);
            }
            return fileName;
        }
    }
    internal class ReadData
    {
        public string Name;
        public List<string> Files = new List<string>();
        public List<string[]> Data = new List<string[]>();
        public DataTableFieldHeader Header;
    }
    public class ClassInfo
    {
        public string Name;
        public string Define;
        public string[] Branchs;
        public List<FieldInfo> Fields = new List<FieldInfo>();
        public FieldInfo Index;
        public int GetFieldCount()
        {
            int c = 0;
            foreach (var item in Fields)
            {
                if (Index != null)
                {
                    c += Index.Name != item.Name ? 1 : 0;
                }
                else
                {
                    c++;
                }

            }
            return c;
        }
    }
    public class DataInfo
    {
        public string Name;
        public int Count { get { return Data.Count; } }
        public string Define;
        public string Branch;
        public ClassInfo ClassInfo { get; private set; }
        internal List<string[]> Data = new List<string[]>();
        private int fieldCount;
        public DataInfo()
        {

        }
        public string[] GetLine(int index)
        {
            if (index < 0 || index >= Data.Count)
            {
                return null;
            }
            var line = new string[fieldCount];
            var data = Data[index];
            for (var i = 0; i < fieldCount; i++)
            {
                var f = ClassInfo.Fields[i];
                var id = f.GetBranchIndex(Branch);
                line[i] = data[id];
            }

            return line;
        }
        internal void SetClassInfo(ClassInfo info)
        {
            ClassInfo = info;
            fieldCount = ClassInfo.Fields.Count;
        }
    }
    public class ProcessData
    {
        public string Path;
        public bool Finish;
        public string Define;
        public List<string> Tables;
        public List<ClassInfo> ClassInfos;
        public List<DataInfo> DataInfos;
        public ProcessData()
        {
            Tables = new List<string>();
            ClassInfos = new List<ClassInfo>();
            DataInfos = new List<DataInfo>();
        }
    }
    public partial class TableBuildPipline
    {
        private const string KEY = "DataTable.BuildPipline";
        public BuildContext Context { get; private set; }
        private List<PiplineBase> list = new List<PiplineBase>();
        private List<PiplineBase> finishList = new List<PiplineBase>();
        private static IDataTableProcessor _processor;

        public TableBuildPipline()
        {
            if (_processor == null)
            {
                var processors = new List<IDataTableProcessor>();
                var typelist = Utils.Assembly.GetEditorTypes(typeof(IDataTableProcessor));
                foreach (var item in typelist)
                {
                    if (item == typeof(BuiltinProcessor)) continue;
                    if (System.Activator.CreateInstance(item) is IDataTableProcessor cmd)
                    {
                        processors.Add(cmd);
                    }
                }
                if (processors.Count < 1)
                {
                    Debug.LogError("需要定义一个数据表处理器子类 Framework.DataTable.DataTableProcessor\n或者实现一个数据表处理器接口 Framework.DataTable.IDataTableProcessor");
                }
                else
                {
                    _processor = processors[0];
                }
            }


            Context = new BuildContext();
            Context.Processor = _processor;
            list.Add(new PiplineSetup());
            list.Add(new PiplineCopyAllToCache());
            list.Add(new PiplineRead());
            list.Add(new PiplineGenInfo());
            list.Add(new PiplineGenCode());
            list.Add(new PiplineGenData());


        }

        public void BuildFile(string path)
        {
            Context.BuildFile = path;
        }
        public void Execute()
        {
            if (Context.Processor == null)
            {
                return;
            }
            var len = list.Count;
            for (var i = 0; i < len; i++)
            {
                var item = list[i];
                item.Context = Context;
                if (!item.Execute())
                {

                    Context.LogError("exe " + item.GetType().Name + " error");
                    return;
                }
                finishList.Add(item);
                if (EditorApplication.isCompiling)
                {
                    EditorPrefs.SetString(KEY, Context.BuildFile);
                    Cleanup();
                    return;
                }
            }
            Cleanup();
            Context.LogWarning("success");

            if (_instance == null)
            {
                _instance = new TableBuildPipline();
                _instance.Watch();
            }
        }
        private void Cleanup()
        {
            foreach (var item in finishList)
            {
                item.Context = Context;
                item.Cleanup();
            }
        }
        [DidReloadScripts]
        private static void OnReloadScripts()
        {
            if (EditorPrefs.HasKey(KEY))
            {
                var path = EditorPrefs.GetString(KEY);
                EditorPrefs.DeleteKey(KEY);
                var p = new TableBuildPipline();
                p.Context.SkipGenCode = true;
                p.BuildFile(path);
                p.Execute();
            }
            if (_instance == null)
            {
                _instance = new TableBuildPipline();
                _instance.Watch();
            }
        }
    }
    public partial class TableBuildPipline
    {
        private static TableBuildPipline _instance;
        // [InitializeOnLoadMethod]
        private static void Load()
        {
            _instance = new TableBuildPipline();
            _instance.Watch();
        }
        FileSystemWatcher watcher;
        FileSystemEventHandler changedHandler;
        RenamedEventHandler renameHandler;
        private void Watch()
        {
            var p = Path.GetFullPath(Path.Combine(Application.dataPath, "../", "IgnoreBuildTable"));
            if (File.Exists(p))
            {
                return;
            }
            if (watcher != null || Context.Processor == null)
            {
                return;
            }
            watcher = new FileSystemWatcher();
            watcher.Path = Context.Processor.GetWatchDirectory();
            watcher.Filter = "*.*";
            watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
            watcher.IncludeSubdirectories = true;
            changedHandler = new FileSystemEventHandler(OnDataTableChanged);
            renameHandler = new RenamedEventHandler(OnDataTableChanged);
            watcher.Changed += changedHandler;
            watcher.Deleted += changedHandler;
            watcher.Created += changedHandler;
            watcher.Renamed += renameHandler;
            watcher.EnableRaisingEvents = true;
            // Debug.LogError("watch");
        }
        ~TableBuildPipline()
        {
            if (watcher == null)
            {
                return;
            }
            watcher.Changed -= changedHandler;
            watcher.Deleted -= changedHandler;
            watcher.Created -= changedHandler;
            watcher.Renamed -= renameHandler;
            watcher.Dispose();
            // Debug.LogError("destroy");
        }
        private void OnDataTableChanged(object sender, FileSystemEventArgs e)
        {
            var filePath = e.FullPath;
            var fileName = Path.GetFileName(filePath);
            if (fileName.StartsWith("~"))
            {
                return;
            }
            // Debug.LogError("change " + e.FullPath + "  " + e.ChangeType);
            var check = true;
            if (Context.Processor.GetFileType(filePath) == FileType.Unknow)
            {
                check = false;
            }
            var fileNameWithoutExt = Context.GetTableName(fileName);
            if (!Context.NameRegex.IsMatch(fileNameWithoutExt))
            {
                check = false;
            }
            if (!check)
            {
                return;
            }
            if (e.ChangeType == WatcherChangeTypes.Created || e.ChangeType == WatcherChangeTypes.Changed)
            {
                Context.BuildFile = e.FullPath;
                Execute();
            }
            else if (e.ChangeType == WatcherChangeTypes.Deleted)
            {
                Execute();
            }
        }

    }
    internal class PiplineBase
    {
        internal BuildContext Context;
        public virtual bool Execute()
        {
            return false;
        }
        public virtual void Cleanup()
        {

        }
    }
    internal class PiplineSetup : PiplineBase
    {
        public PiplineSetup()
        {

        }
        public override bool Execute()
        {
            if (!Directory.Exists(Context.ProjectPath))
            {
                Context.LogError("project error " + Context.ProjectPath);
                return false;
            }
            if (string.IsNullOrEmpty(Context.TempPath))
            {
                Context.LogError("temp error " + Context.TempPath);
                return false;
            }
            if (!Directory.Exists(Context.TempPath))
            {
                Directory.CreateDirectory(Context.TempPath);
            }
            Context.SourcePath = Context.Processor.GetWatchDirectory();
            if (!Directory.Exists(Context.SourcePath))
            {
                Context.LogError("source error " + Context.SourcePath);
                return false;
            }

            IDataTableProcessor processor = null;
            var processors = new List<IDataTableProcessor>();
            var typelist = Utils.Assembly.GetEditorTypes(typeof(IDataTableProcessor));
            foreach (var item in typelist)
            {
                if (item == typeof(BuiltinProcessor)) continue;
                if (System.Activator.CreateInstance(item) is IDataTableProcessor cmd)
                {
                    processors.Add(cmd);
                }
            }
            if (processors.Count < 1)
            {
                Context.LogError("需要定义一个数据表处理器子类 Framework.DataTable.DataTableProcessor\n或者实现一个数据表处理器接口 Framework.DataTable.IDataTableProcessor");
                return false;
            }
            processor = processors[0];
            if (processors.Count == 1)
            {

            }
            else
            {
                Context.LogWarning("不需要多个数据表处理器");
            }
            Context.Processor = processor;
            processor.Setup(Context);
            return true;
        }
    }
    internal class PiplineCopyAllToCache : PiplineBase
    {
        public override bool Execute()
        {
            if (Context.Processor == null)
            {
                return false;
            }

            var fileList = Directory.GetFileSystemEntries(Context.SourcePath, "*.*", SearchOption.AllDirectories);
            Context.SourceClonePath = Path.GetFullPath(Path.Combine(Context.TempPath, "Source"));
            try
            {
                if (Directory.Exists(Context.SourceClonePath))
                {
                    Directory.Delete(Context.SourceClonePath, true);
                }
                Directory.CreateDirectory(Context.SourceClonePath);
            }
            catch (System.Exception)
            {

            }

            foreach (var filePath in fileList)
            {
                if (Directory.Exists(filePath)) continue;
                var fileName = Path.GetFileName(filePath);
                if (filePath.StartsWith("~"))
                {
                    continue;
                }
                if (Context.Processor.GetFileType(fileName) == FileType.Unknow) continue;
                var fileNameWithoutExt = Context.GetTableName(fileName);
                if (!Context.NameRegex.IsMatch(fileNameWithoutExt))
                {
                    Context.LogWarning("skip file " + fileName);
                    continue;
                }
                var dst = Path.Combine(Context.SourceClonePath, fileName);
                try
                {
                    File.Copy(filePath, dst, true);
                }
                catch (System.Exception)
                {

                }
            }
            return true;
        }
    }
    internal class PiplineRead : PiplineBase
    {
        public override bool Execute()
        {
            Context.Tables.Clear();
            Context.ReadPath = Path.GetFullPath(Path.Combine(Context.TempPath, "Read"));
            try
            {
                if (Directory.Exists(Context.ReadPath))
                {
                    Directory.Delete(Context.ReadPath, true);
                }
                Directory.CreateDirectory(Context.ReadPath);
            }
            catch (System.Exception)
            {

            }
            string targetName = null;
            if (!string.IsNullOrEmpty(Context.BuildFile))
            {
                targetName = Context.GetTableName(Context.BuildFile);
            }
            var readMap = new Dictionary<string, ReadData>();
            var fileList = Directory.GetFileSystemEntries(Context.SourceClonePath, "*.*", SearchOption.AllDirectories);
            foreach (var filePath in fileList)
            {
                if (Directory.Exists(filePath)) continue;
                var fileName = Path.GetFileName(filePath);
                var tableName = Context.GetTableName(fileName);
                if (!Context.Tables.Contains(tableName))
                {
                    Context.Tables.Add(tableName);
                }
                if (!string.IsNullOrEmpty(targetName))
                {
                    if (targetName != tableName)
                    {
                        continue;
                    }
                }
                if (!readMap.TryGetValue(tableName, out var meta))
                {
                    meta = new ReadData
                    {
                        Name = tableName
                    };
                    readMap.Add(tableName, meta);
                    Context.FileInfos.Add(meta);
                }
                meta.Files.Add(fileName);
            }
            foreach (var item in readMap)
            {
                var read = item.Value;
                read.Files.Sort((a, b) => { return b.CompareTo(a); });
                foreach (var f in read.Files)
                {
                    DataTableConfigBase config = null;
                    var fileType = Context.Processor.GetFileType(f);
                    if (fileType == FileType.Excel)
                    {
                        config = new DataTableConfigExcel();
                    }
                    else if (fileType == FileType.Text)
                    {
                        config = new DataTableConfigTsv();
                    }
                    if (config == null)
                    {
                        continue;
                    }
                    config.SetTableName(read.Name);
                    config.ReadFile(Path.Combine(Context.SourceClonePath, f));
                    if (!config.Enabled)
                    {
                        return false;
                    }
                    config.Header.Init();
                    if (read.Header != null)
                    {
                        if (read.Header.GetHeaderVersion() != config.Header.GetHeaderVersion())
                        {
                            Context.LogError("header error " + f);
                        }
                    }
                    read.Header = config.Header;
                    read.Data.AddRange(config.DataInfos);
                }
            }
            var builder = new StringBuilder();
            foreach (var item in readMap)
            {
                var read = item.Value;
                var path = Path.Combine(Context.ReadPath, read.Name + ".txt");
                builder.Clear();
                foreach (var lines in read.Data)
                {
                    var len = lines.Length;
                    for (var i = 0; i < len; i++)
                    {
                        builder.Append((i > 0 ? "\t" : "") + lines[i]);
                    }
                    builder.AppendLine();
                }
                try
                {
                    File.WriteAllText(path, builder.ToString(), System.Text.Encoding.UTF8);
                }
                catch (System.Exception)
                {
                    Context.LogError("write file error " + path);
                    return false;
                }
            }
            Context.Tables.Sort();
            return true;
        }
    }
    internal class PiplineGenInfo : PiplineBase
    {
        public override bool Execute()
        {
            Context.classInfos.Clear();
            Context.dataInfos.Clear();
            foreach (var item in Context.FileInfos)
            {
                var header = item.Header;

                foreach (var define in header.Defines)
                {
                    var info = new ClassInfo
                    {
                        Name = item.Name,
                        Define = define,
                        Branchs = header.DataBranchs.ToArray(),
                        Index = header.IndexField
                    };
                    foreach (var f in header.FieldInfos)
                    {
                        var finfo = f.Clone();
                        if (finfo.HasDefine(define))
                        {
                            finfo.DeclarIndex = info.Fields.Count;
                            info.Fields.Add(finfo);
                        }
                    }
                    Context.classInfos.Add(info);
                    foreach (var branch in header.DataBranchs)
                    {
                        var data = new DataInfo();
                        data.SetClassInfo(info);
                        data.Define = define;
                        data.Branch = branch;
                        data.Name = item.Name;
                        data.Data.AddRange(item.Data);

                        Context.dataInfos.Add(data);
                    }
                }
            }
            return true;
        }
    }

    internal class PiplineGenCode : PiplineBase
    {
        private List<ProcessData> processDatas = new List<ProcessData>();
        public override bool Execute()
        {
            if (Context.SkipGenCode)
            {
                return true;
            }
            var defineMap = new Dictionary<string, List<ClassInfo>>();
            foreach (var item in Context.classInfos)
            {
                if (!defineMap.TryGetValue(item.Define, out var list))
                {
                    list = new List<ClassInfo>();
                    defineMap.Add(item.Define, list);
                }
                list.Add(item);
            }
            foreach (var item in defineMap)
            {
                var d = new ProcessData();
                d.Define = item.Key;
                d.Tables.AddRange(Context.Tables);
                d.ClassInfos.AddRange(item.Value);
                Context.Processor.BuildCode(d);
                processDatas.Add(d);
            }

            return true;
        }
        public override void Cleanup()
        {
            base.Cleanup();
            foreach (var item in processDatas)
            {
                Context.Processor.Cleanup(Context, item);
            }
            processDatas.Clear();
        }
    }
    internal class PiplineGenData : PiplineBase
    {
        private List<ProcessData> processDatas = new List<ProcessData>();
        public override bool Execute()
        {
            var dataMap = new Dictionary<string, List<DataInfo>>();
            foreach (var item in Context.dataInfos)
            {
                if (!dataMap.TryGetValue(item.Define, out var list))
                {
                    list = new List<DataInfo>();
                    dataMap.Add(item.Define, list);
                }
                list.Add(item);
            }
            foreach (var item in dataMap)
            {
                var d = new ProcessData();
                d.Define = item.Key;
                d.Tables.AddRange(Context.Tables);
                d.DataInfos.AddRange(item.Value);
                Context.Processor.BuildData(d);
                processDatas.Add(d);
            }
            return true;
        }
        public override void Cleanup()
        {
            base.Cleanup();
            foreach (var item in processDatas)
            {
                Context.Processor.Cleanup(Context, item);
            }
            processDatas.Clear();
        }

    }

}
